﻿using System.Collections.Concurrent;

namespace Sgr.Utilities
{
    /// <summary>
    /// 提供防抖动（Debounce）功能的工具类，用于延迟执行操作并在连续触发时只执行最后一次
    /// </summary>
    public static class DebounceHelper
    {
        private static readonly ConcurrentDictionary<string, CancellationTokenSource> _pendingOperations = new();

        /// <summary>
        /// 执行带返回值的防抖操作，延迟指定时间后执行，如果在延迟期间再次调用则取消前一次操作
        /// </summary>
        /// <typeparam name="T">操作返回值类型</typeparam>
        /// <param name="key">用于标识防抖操作的唯一键</param>
        /// <param name="operation">要执行的异步操作</param>
        /// <param name="interval">延迟执行的时间间隔</param>
        /// <param name="cancellationToken">可用于取消操作的令牌</param>
        /// <returns>操作的结果</returns>
        /// <exception cref="ArgumentNullException">当key或operation为null时抛出</exception>
        /// <exception cref="ArgumentOutOfRangeException">当interval为负数时抛出</exception>
        public static async Task<T> DebounceAsync<T>(
            string key,
            Func<CancellationToken, Task<T>> operation,
            TimeSpan interval,
            CancellationToken cancellationToken = default)
        {
            Check.StringNotNullOrEmpty(key, nameof(key));
            Check.NotNull(operation, nameof(operation));

            if (interval < TimeSpan.Zero)
                throw new ArgumentOutOfRangeException(nameof(interval), "延迟时间间隔不能为负值");

            // 取消并处理已存在的操作
            if (_pendingOperations.TryGetValue(key, out var existingCts))
            {
                try
                {
                    existingCts.Cancel();
                }
                catch (ObjectDisposedException)
                {
                    // 令牌可能已被处理，忽略该异常
                }
                finally
                {
                    existingCts.Dispose();
                }
            }

            // 创建新的取消令牌并保存
            var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
            _pendingOperations[key] = linkedCts;

            try
            {
                await Task.Delay(interval, linkedCts.Token).ConfigureAwait(false);
                return await operation(linkedCts.Token).ConfigureAwait(false);
            }
            catch (TaskCanceledException)
            {
                // 如果是因为我们自己的取消令牌被取消，则抑制异常
                if (linkedCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested)
                    throw new OperationCanceledException("操作被新的请求取消", cancellationToken);
                throw;
            }
            finally
            {
                // 从字典中移除并释放资源
                _pendingOperations.TryRemove(key, out _);
                linkedCts.Dispose();
            }
        }

        /// <summary>
        /// 执行无返回值的防抖操作，延迟指定时间后执行，如果在延迟期间再次调用则取消前一次操作
        /// </summary>
        /// <param name="key">用于标识防抖操作的唯一键</param>
        /// <param name="operation">要执行的异步操作</param>
        /// <param name="interval">延迟执行的时间间隔</param>
        /// <param name="cancellationToken">可用于取消操作的令牌</param>
        /// <returns>表示异步操作的任务</returns>
        /// <exception cref="ArgumentNullException">当key或operation为null时抛出</exception>
        /// <exception cref="ArgumentOutOfRangeException">当interval为负数时抛出</exception>
        public static async Task DebounceAsync(
            string key,
            Func<CancellationToken, Task> operation,
            TimeSpan interval,
            CancellationToken cancellationToken = default)
        {
            Check.StringNotNullOrEmpty(key, nameof(key));
            Check.NotNull(operation, nameof(operation));

            if (interval < TimeSpan.Zero)
                throw new ArgumentOutOfRangeException(nameof(interval), "延迟时间间隔不能为负值");

            // 取消并处理已存在的操作
            if (_pendingOperations.TryGetValue(key, out var existingCts))
            {
                try
                {
                    existingCts.Cancel();
                }
                catch (ObjectDisposedException)
                {
                    // 令牌可能已被处理，忽略该异常
                }
                finally
                {
                    existingCts.Dispose();
                }
            }

            // 创建新的取消令牌并保存
            var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
            _pendingOperations[key] = linkedCts;

            try
            {
                await Task.Delay(interval, linkedCts.Token).ConfigureAwait(false);
                await operation(linkedCts.Token).ConfigureAwait(false);
            }
            catch (TaskCanceledException)
            {
                // 如果是因为我们自己的取消令牌被取消，则抑制异常
                if (linkedCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested)
                    throw new OperationCanceledException("操作被新的请求取消", cancellationToken);
                throw;
            }
            finally
            {
                // 从字典中移除并释放资源
                _pendingOperations.TryRemove(key, out _);
                linkedCts.Dispose();
            }
        }

        /// <summary>
        /// 执行带返回值的防抖操作，支持配置是否立即执行首次操作
        /// </summary>
        /// <typeparam name="T">操作返回值类型</typeparam>
        /// <param name="key">用于标识防抖操作的唯一键</param>
        /// <param name="operation">要执行的异步操作</param>
        /// <param name="interval">延迟执行的时间间隔</param>
        /// <param name="executeImmediately">是否立即执行首次操作</param>
        /// <param name="cancellationToken">可用于取消操作的令牌</param>
        /// <returns>操作的结果</returns>
        public static async Task<T> DebounceAsync<T>(
            string key,
            Func<CancellationToken, Task<T>> operation,
            TimeSpan interval,
            bool executeImmediately,
            CancellationToken cancellationToken = default)
        {
            if (!executeImmediately)
                return await DebounceAsync(key, operation, interval, cancellationToken).ConfigureAwait(false);

            // 如果需要立即执行且没有挂起的相同键的操作
            if (!_pendingOperations.ContainsKey(key))
            {
                var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
                _pendingOperations[key] = linkedCts;

                try
                {
                    return await operation(linkedCts.Token).ConfigureAwait(false);
                }
                finally
                {
                    _pendingOperations.TryRemove(key, out _);
                    linkedCts.Dispose();
                }
            }

            // 如果已有挂起操作，则按正常防抖逻辑处理
            return await DebounceAsync(key, operation, interval, cancellationToken).ConfigureAwait(false);
        }

        /// <summary>
        /// 执行无返回值的防抖操作，支持配置是否立即执行首次操作
        /// </summary>
        /// <param name="key">用于标识防抖操作的唯一键</param>
        /// <param name="operation">要执行的异步操作</param>
        /// <param name="interval">延迟执行的时间间隔</param>
        /// <param name="executeImmediately">是否立即执行首次操作</param>
        /// <param name="cancellationToken">可用于取消操作的令牌</param>
        /// <returns>表示异步操作的任务</returns>
        public static async Task DebounceAsync(
            string key,
            Func<CancellationToken, Task> operation,
            TimeSpan interval,
            bool executeImmediately,
            CancellationToken cancellationToken = default)
        {
            if (!executeImmediately)
                await DebounceAsync(key, operation, interval, cancellationToken).ConfigureAwait(false);

            // 如果需要立即执行且没有挂起的相同键的操作
            if (!_pendingOperations.ContainsKey(key))
            {
                var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
                _pendingOperations[key] = linkedCts;

                try
                {
                    await operation(linkedCts.Token).ConfigureAwait(false);
                }
                finally
                {
                    _pendingOperations.TryRemove(key, out _);
                    linkedCts.Dispose();
                }
                return;
            }

            // 如果已有挂起操作，则按正常防抖逻辑处理
            await DebounceAsync(key, operation, interval, cancellationToken).ConfigureAwait(false);
        }

        /// <summary>
        /// 清除指定键的挂起操作
        /// </summary>
        /// <param name="key">要清除的操作键</param>
        /// <returns>如果成功清除返回true，否则返回false</returns>
        public static bool ClearPendingOperation(string key)
        {
            Check.StringNotNullOrEmpty(key, nameof(key));

            if (_pendingOperations.TryRemove(key, out var cts))
            {
                try
                {
                    cts.Cancel();
                    cts.Dispose();
                    return true;
                }
                catch
                {
                    return false;
                }
            }
            return false;
        }

        /// <summary>
        /// 清除所有挂起的操作
        /// </summary>
        public static void ClearAllPendingOperations()
        {
            var keys = _pendingOperations.Keys.ToArray();
            foreach (var key in keys)
            {
                ClearPendingOperation(key);
            }
        }
    }
}